﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using System.Xml.Linq;

namespace KATS_Client.Client
{
    /// <summary>
    /// The queue of file actions that are waiting to be processed
    /// </summary>
    public static class FileActionsQueue
    {
        /// <summary>A queued item has returned an update</summary>
        public static event EventHandler<FileActionProgress> FileActionUpdate;
        /// <summary>A queued item has completed</summary>
        public static event EventHandler<FileActionProgress> FileActionCompleted;
        /// <summary>The queue has changed</summary>
        public static event EventHandler<EventArgs> QueueChanged;
        /// <summary>The queue has loaded</summary>
        public static event EventHandler<EventArgs> QueueLoaded;

        /// <summary>The queue</summary>
        public static List<FileActionData> QueuedActions;

        /// <summary>Is the queue currently processing?</summary>
        private static bool active = false;

        /// <summary>Is the queue currently processing?</summary>
        public static bool Active
        {
            get { return active; }
            set
            {
                active = value;
                if (Active_Changed != null)
                    Active_Changed(new object(), new EventArgs());
            }
        }
        /// <summary>The active property has been changed</summary>
        public static event EventHandler<EventArgs> Active_Changed;

        /// <summary>The current actions progress</summary>
        public static FileActionProgress CurrentActionProgress = new FileActionProgress(0, string.Empty);

        /// <summary>The current action</summary>
        public static FileActionData CurrentAction;




        /// <summary>The static constructor for this class</summary>
        static FileActionsQueue()
        {
            // set the host commands to its valid value
            QueuedActions = new List<FileActionData>();



        }

        /// <summary>The program is exiting</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void Program_Exiting(object sender, EventArgs e)
        {
            if (SaveQueue_Timer != null)
            {
                SaveQueue_Timer.Close();
                SaveQueue_Timer = null;
                Save();
            }
        }

        /// <summary>Initialize the queue in a new thread</summary>
        public static void Initialize_Threaded()
        {
            Thread initialize = new Thread(new ThreadStart(Initialize));
            initialize.Name = "File Actions Queue Starting...";
            initialize.IsBackground = true;
            initialize.Start();
        }

        /// <summary>Initialise the queue</summary>
        public static void Initialize()
        {
            // load the host commands data
            Load();
            int rv = ProgressReporting.Start("Queue starting");
            // start a new thread to monitor and handle the transfer queue
            Thread QueueThread = new Thread(new ThreadStart(ProcessQueue));
            QueueThread.Name = "File Actions Queue Processing";
            QueueThread.IsBackground = true;
            QueueThread.Start();
            ProgressReporting.End(rv, "Queue started");
            QueueChanged += new EventHandler<EventArgs>(QueueListChanged);

            Program.Exiting += new EventHandler<EventArgs>(Program_Exiting);
        }

        /// <summary>The queue list has been changed</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void QueueListChanged(object sender, EventArgs e)
        {
            if (SaveQueue_Timer == null)
            {
                // not currently waiting to save the queue
                // so create and start a save timer
                SaveQueue_Timer = new System.Timers.Timer(5000);
                SaveQueue_Timer.AutoReset = false;
                SaveQueue_Timer.Elapsed += new System.Timers.ElapsedEventHandler(SaveQueue_Timer_Elapsed);
                SaveQueue_Timer.Enabled = true;
            }
        }

        /// <summary>The timer to save the queue has trigered</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void SaveQueue_Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            Save();
            SaveQueue_Timer.Close();
            SaveQueue_Timer = null;
        }

        /// <summary>The timer for the save queue</summary>
        static System.Timers.Timer SaveQueue_Timer;

        /// <summary>The number of items in the queue</summary>
        public static int Count
        {
            get
            {
                return QueuedActions.Count;
            }
        }

        /// <summary>Processes the file action queue</summary>
        private static void ProcessQueue()
        {
            while (true) // repeat forever
            {
                if (!Active)
                {
                    // if either the server is offline or the processing is stopped
                    // don't process the queue
                    Thread.Sleep(1000);
                    continue;
                }
                int numberCommands = 0;

                lock (QueuedActions) // lock the list to retrieve the current list size
                {
                    numberCommands = QueuedActions.Count;
                }

                while (numberCommands > 0) // process the current file actions
                {
                    if (!Active)
                    {
                        // if either the server is offline or the processing is stopped
                        // don't process the queue
                        break;
                    }

                    FileActionData currentQueueItem = null;

                    // find an event to process
                    int i = 0;
                    bool found = false;
                    while (i < numberCommands && found == false)
                    {
                        lock (QueuedActions)
                        {
                            currentQueueItem = QueuedActions[i];
                        }
                        double timeSinceProcessTime = (DateTime.Now - currentQueueItem.ProcessTime).TotalMilliseconds;
                        if (timeSinceProcessTime < 0)
                        {
                            // there is still processing time remaining for this object
                            i++; // check next item
                            continue;
                        }
                        try
                        {
                            // attempt to access the file
                            // this will throw error if the file has a restrictive lock placed 
                            // on it by another program. 
                            // [i.e. This file would not be able to be processed if this errors
                            long size;
                            if (currentQueueItem.Location.IsRemote == false && currentQueueItem.Location.objectType == FileSystemObjectTypes.File)
                                size = new FileInfo(currentQueueItem.Location.Location).Length;
                        }
                        catch (IOException)
                        {
                            // file conflict, therefore try the next file
                            i++;
                            continue;
                        }
                        found = true;
                        CurrentAction = currentQueueItem;
                    }

                    if (CurrentAction == null)
                    {
                        break;
                    }

                    if (CurrentAction.RequiresServer)
                    {
                        if (!ServerConnection.ServerStatus)
                        {
                            // the activity requires the server, but server offline
                            // cancel and restart
                            CurrentAction = null;
                            break;
                        }
                    }

                    // process the event

                    bool successful = false;
                    switch (CurrentAction.ActionType) // call the function for the current event
                    {
                        case ActionTypes.CopyFile:
                            successful = FileActions.CopyFile(CurrentAction.Location, CurrentAction.NewLocation);
                            break;
                        case ActionTypes.Delete:
                            successful = FileActions.Delete(CurrentAction.Location);
                            break;
                        case ActionTypes.Move:
                            successful = FileActions.Move(CurrentAction.Location, CurrentAction.NewLocation);
                            break;
                        case ActionTypes.Rename:
                            successful = FileActions.Move(CurrentAction.Location, CurrentAction.NewLocation);
                            break;
                    }

                    // if event successful remove event from list and reduce the numberCommands

                    currentQueueItem = null;

                    if (successful)
                    {
                        lock (QueuedActions) // lock to make thread safe
                        {
                            QueuedActions.Remove(CurrentAction);
                        }
                        if (QueueChanged != null)
                            QueueChanged(new object(), new EventArgs());
                        if (FileActionCompleted != null) // trigger the event completed event
                            FileActionCompleted(CurrentAction, new FileActionProgress(100, ""));

                        CurrentAction = null;
                        numberCommands--;// reduce the number of commands avaiting processing
                    }
                    else
                    {
                        Thread.Sleep(3000);
                    }

                }
                Thread.Sleep(1000); // sleep for a while then check for more events
            }

        }

        /// <summary>Get a string to represent the queue</summary>
        /// <param name="start"></param>
        /// <param name="number"></param>
        /// <returns></returns>
        public static string GenerateQueueText(int start, int number)
        {
            string msg = string.Empty;

            int end = start + number;
            lock (Client.FileActionsQueue.QueuedActions)
            {
                if (Client.FileActionsQueue.QueuedActions.Count == 0)
                    return string.Empty;
                if (Client.FileActionsQueue.QueuedActions.Count < end)
                    end = Client.FileActionsQueue.QueuedActions.Count;

                for (int i = start; i < end; i++)
                {
                    Client.FileActionData fad = Client.FileActionsQueue.QueuedActions[i];
                    msg += fad.ActionType.ToString() + "  ";
                    if (fad.Location.IsRemote)
                        msg += "R";
                    else
                        msg += "L";

                    if (fad.NewLocation != null)
                    {
                        msg += " -> ";
                        if (fad.NewLocation.IsRemote)
                            msg += "R";
                        else
                            msg += "L";
                    }
                    msg += "\n";
                    msg += fad.Location.Name + "\n\n";
                }
            }
            return msg;
        }


        /// <summary>Trigger the FileActionCompleted event</summary>
        /// <param name="fileActionData">The file action that has completed</param>
        public static void Trigger_FileActionCompleted(FileActionData fileActionData)
        {
            if (FileActionCompleted != null)
                FileActionCompleted(fileActionData, new FileActionProgress(100, ""));
        }

        /// <summary>Remove an item from the processing queue</summary>
        /// <param name="index">Position</param>
        public static void RemoveAt(int index)
        {
            if (index < 0)
                return;
            lock (QueuedActions)
            {
                QueuedActions.RemoveAt(index);
            }
        }

        /// <summary>Add an item to the processing queue</summary>
        /// <param name="fileActionData">The item to add</param>
        private static void Add(FileActionData fileActionData)
        {
            lock (QueuedActions)
            {
                QueuedActions.Add(fileActionData);
            }
        }

        /// <summary>Clear the queue list</summary>
        public static void Clear()
        {
            lock (QueuedActions)
            {
                QueuedActions.Clear();
            }
            if (QueueChanged != null)
                QueueChanged(new object(), new EventArgs());
        }

        /// <summary>Save the queue</summary>
        public static void Save()
        {
            //int rv = ProgressReporting.Start("Saving queue");
            try
            {
                List<XElement> elements = new List<XElement>();
                lock (QueuedActions) // lock to make threadsafe
                {
                    foreach (FileActionData fad in QueuedActions)
                    {
                        XElement x = fad.ToXML();
                        elements.Add(x);
                    }
                }

                XDocument queueDocument =
                     new XDocument(
                         new XDeclaration("1.0", "uft-8", "yes"),
                         new XComment("The file action queue."),
                         new XElement("Items",
                         elements.ToArray()));
                queueDocument.Save(Properties.Settings.Default.FileActionsQueue);
                //ProgressReporting.End(rv, "Queue data saved");
            }
            catch (Exception e)
            {
                // catch any errors and display them on the console
                ProgressReporting.Failed("Could not save FileTransfer Command List to file\n-> " + e.Message);
                Console.WriteLine(e.StackTrace);
            }
        }

        /// <summary>Load the queue</summary>
        public static void Load()
        {
            int rv = ProgressReporting.Start("Loading queue");
            try
            {
                XDocument document = XDocument.Load(Properties.Settings.Default.FileActionsQueue);
                XElement watchers = document.Element("Items");

                foreach (XElement item in watchers.Nodes())
                {
                    // restore the watcher from the xml save
                    QueuedActions.Add(new FileActionData(item));
                }
                if (QueueLoaded != null)
                    QueueLoaded(new object(), new EventArgs());
                ProgressReporting.End(rv, "Queue loaded");
            }
            catch (Exception e)
            {
                // catch any errors and display them on the console
                ProgressReporting.Failed("Could not load Queue from file\n-> " + e.Message);
                Console.WriteLine(e.StackTrace);
            }
        }




        /// <summary>Trigger the command list changed event</summary>
        public static void Trigger_Queue_Changed()
        {
            if (QueueChanged != null)
                QueueChanged(new object(), new EventArgs());
        }

        /// <summary>Trigger the transfer progress to be updated</summary>
        /// <param name="progress">The percentage progress</param>
        /// <param name="speed">The current speed</param>
        public static void Trigger_Transfer_Update(int? progress, string speed)
        {
            if (progress.HasValue == true)
                CurrentActionProgress.PercentageComplete = progress.Value;
            if (speed != null)
                CurrentActionProgress.Speed = speed;

            if (FileActionUpdate != null)
                FileActionUpdate(new object(), CurrentActionProgress);
        }


        /// <summary>Copy a folder [Queue based]</summary>
        /// <param name="Source">The source folder</param>
        /// <param name="Target">The target folder</param>
        public static void CopyFolder(FileSystemObject Source, FileSystemObject Target)
        {
            if (Source.IsRemote && Target.IsRemote)
            {
                // server to server copying
                FileActionData fad = new FileActionData(ActionTypes.CopyFile, Source, Target);
                Add(fad);
                Trigger_Queue_Changed();
            }
            else if (Source.IsRemote && Target.IsLocal)
            {
                // server -> client copying
                GetDirectory(Source, Target);
            }
            else if (Source.IsLocal && Target.IsRemote)
            {
                // client -> server copying
                TransferDirectory(Source, Target);
            }
            else
            {
                // client -> client copying
                FileActionData fad = new FileActionData(ActionTypes.CopyFile, Source, Target);
                Add(fad);
                Trigger_Queue_Changed();
            }
        }

        /// <summary>Transfer (copies) directory to server [uses QUEUE processing]</summary>
        /// <param name="LocalDirectory">Local directory</param>
        /// <param name="RemoteDirectory">Remote directory</param>
        private static void TransferDirectory(FileSystemObject LocalDirectory, FileSystemObject RemoteDirectory)
        {
            DirectoryInfo currentDirectory = new DirectoryInfo(LocalDirectory.Location);
            DirectoryInfo[] subDirectories = currentDirectory.GetDirectories();
            foreach (DirectoryInfo subFolder in subDirectories)
            {
                FileSystemObject fso = new FileSystemObject(subFolder.FullName, false, FileSystemObjectTypes.Directory);
                FileSystemObject fso2 = new FileSystemObject(RemoteDirectory.Location + "\\" + subFolder.Name, true, FileSystemObjectTypes.Directory);

                TransferDirectory(fso, fso2); // transfer all subfolders
            }

            FileActions.CreateDirectory(RemoteDirectory);

            FileInfo[] files = currentDirectory.GetFiles();
            // queue the files in the current folder for transfer
            foreach (FileInfo file in files)
            {
                FileSystemObject fso = new FileSystemObject(file.FullName, false, FileSystemObjectTypes.File);
                FileSystemObject fso2 = new FileSystemObject(RemoteDirectory.Location + "\\" + file.Name, true, FileSystemObjectTypes.File);
                CopyFile(fso, fso2);
            }
        }

        /// <summary>Retrieves (copies) a directory and all its files from the server [uses QUEUE processing]</summary>
        /// <param name="RemoteFolder">Remote directory</param>
        /// <param name="LocalFolder">Local directory</param>
        private static void GetDirectory(FileSystemObject RemoteDirectory, FileSystemObject LocalDirectory)
        {
            DirectoryInfo localFolder = new DirectoryInfo(LocalDirectory.Location);
            if (localFolder.Exists == false) // create the folder if it does not exist
                localFolder.Create();

            string[] RemoteSubFolders = FileActions.Directories(RemoteDirectory);

            foreach (string s in RemoteSubFolders)
            {
                DirectoryInfo di = new DirectoryInfo(s);

                FileSystemObject fso = new FileSystemObject(s, true, FileSystemObjectTypes.Directory);
                FileSystemObject fso2 = new FileSystemObject(LocalDirectory.Location + "\\" + di.Name, false, FileSystemObjectTypes.Directory);

                GetDirectory(fso, fso2);
            }

            string[] Remotefiles = FileActions.Files(RemoteDirectory);

            // queue the files in the current folder for retrieval
            foreach (string file in Remotefiles)
            {
                FileInfo fi = new FileInfo(file);
                FileSystemObject fso = new FileSystemObject(file, true, FileSystemObjectTypes.File);
                FileSystemObject fso2 = new FileSystemObject(LocalDirectory.Location + "\\" + fi.Name, false, FileSystemObjectTypes.File);
                CopyFile(fso, fso2);
            }
        }

        /// <summary>Move object to server and delete the local copy [uses QUEUE processing]</summary>
        /// <param name="Location">Location</param>
        /// <param name="NewLocation">New Location</param>
        public static void Move(FileSystemObject Location, FileSystemObject NewLocation)
        {
            // make the file information
            FileActionData fad = new FileActionData(ActionTypes.Move, Location, NewLocation);

            Add(fad);
            Trigger_Queue_Changed();
        }

        /// <summary>Copies a file [uses QUEUE processing]</summary>
        /// <param name="Location">Current Location</param>
        /// <param name="NewLocation">New Location</param>
        public static void CopyFile(FileSystemObject Location, FileSystemObject NewLocation)
        {
            // make the file information
            FileActionData fad = new FileActionData(ActionTypes.CopyFile, Location, NewLocation);

            Add(fad);
            Trigger_Queue_Changed();
        }

        /// <summary>Copy file, after a certain delay. [uses QUEUE processing]</summary>
        /// <param name="Location">The location</param>
        /// <param name="NewLocation">The new location</param>
        /// <param name="delayInSeconds">The delay in seconds</param>
        public static void CopyFile(FileSystemObject Location, FileSystemObject NewLocation, int delayInSeconds)
        {
            // create the FileEventData object for the processing list
            FileActionData fad = new FileActionData(ActionTypes.CopyFile, Location, NewLocation);
            fad.WaitSeconds(delayInSeconds);

            Add(fad);
            Trigger_Queue_Changed();
        }

        /// <summary>Delete file system object</summary>
        /// <param name="Location">The object to delete</param>
        public static void Delete(FileSystemObject Location)
        {
            // create the FileEventData object for the processing list

            FileActionData fad = new FileActionData(ActionTypes.Delete, Location);

            Add(fad);
            Trigger_Queue_Changed();
        }

        /// <summary>Rename an object</summary>
        /// <param name="Location">The object to rename</param>
        /// <param name="NewName">The new name for the object</param>
        public static void Rename(FileSystemObject Location, string NewName)
        {
            // create the FileEventData object for the processing list
            if (Location.objectType == FileSystemObjectTypes.Directory)
            {
                // rename directory
                DirectoryInfo di = new DirectoryInfo(Location.Location);

                string newDirectoryName = string.Format("{0}\\{1}", di.Parent.FullName, NewName);
                FileSystemObject NewLocation = new FileSystemObject(newDirectoryName, Location.IsRemote, FileSystemObjectTypes.Directory);
                FileActionData fad = new FileActionData(ActionTypes.Rename, Location, NewLocation);

                Add(fad);
                Trigger_Queue_Changed();
            }
            else if (Location.objectType == FileSystemObjectTypes.File)
            {
                // rename file
                FileInfo fi = new FileInfo(Location.Location);

                string newFileName = string.Format("{0}\\{1}", fi.Directory.FullName, NewName);
                FileSystemObject NewLocation = new FileSystemObject(newFileName, Location.IsRemote, FileSystemObjectTypes.File);
                FileActionData fad = new FileActionData(ActionTypes.Rename, Location, NewLocation);

                Add(fad);
                Trigger_Queue_Changed();
            }
        }
    }
}
